/*
 * hkasm - A universal algebraic user defined assembler (for microcontrollers etc....)
 * Copyright (C) 2009  Henri Koskela <henri.koskela@iki.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections;
using System.Text.RegularExpressions;


/**
 * Capsulates all from opcode
 * Handle parametrs (absolute, relative to address, copy (previous) and registerparameterR16-R32)
 * Compiles code to binary
 **/
class OpCode{
	private enum parTypes{direct,adrRel,copy,val16to32,sameAs_A,sameAs_B,sameAs_C,sameAs_D}
	private Regex filter;
	public string binpattern;
	private string description;
	private parTypes[] paratypes;
		
	/**
	 * Store binary value to binary pattern
	 * Example:
	 * pattern="AAABBBAAA"
	 * symbol='B'
	 * value=3
	 * -> return value is "AAA011AAA"
	 * 
	 */
	private string storePar(string pattern,char symbol,long value){
		String result=pattern;
			
		int sizeInBits=(pattern.Split(symbol).Length-1);
		if (value<0)  value+=1<<sizeInBits;
		string s="";
		try{
			s=BinOperations.number2binString(value,sizeInBits);  //TÃ¤mÃ¤ raisettaa jos ei pysty
		}catch{
			new Exception("Value "+value+" with symbol "+symbol.ToString()+" won't fit into pattern "+pattern);	
		}
			
		for(int i=0;i<result.Length;i++){
			if (result[i]==symbol){
				result=result.Remove(i,1);
				result=result.Insert(i,s[0].ToString());
				s=s.Remove(0,1);
			}
		}
		return result;
	}

	/**
	 * Parse .opcode directive to opcode
	 */
	public OpCode(string row,string description){
		string s=row.Replace(".opcode ","");
		string[] s_tok=s.Split('\"');
		ArrayList a_tok=new ArrayList();
			
		for(int i=0;i<s_tok.Length;i++){
			if(s_tok[i].Trim().Length>0)
				a_tok.Add(s_tok[i]);
		}
			
		this.filter=new Regex("^"+(string)a_tok[0]+"$");
		this.binpattern=((string)a_tok[1]).Replace(" ","").Trim().ToUpper();
		this.description=description;
		this.paratypes=new parTypes[a_tok.Count-2];
			
		for(int i=0;i<this.paratypes.Length;i++){
			switch((string)a_tok[i+2]){
				case "ABS": this.paratypes[i]=parTypes.direct; break;
				case "REL": this.paratypes[i]=parTypes.adrRel; break;
				case "REG": this.paratypes[i]=parTypes.direct; break;
				case "START16": this.paratypes[i]=parTypes.val16to32; break;
				case "COPY": this.paratypes[i]=parTypes.copy; break;
				case "SAME1": this.paratypes[i]=parTypes.sameAs_A; break;
				case "SAME2": this.paratypes[i]=parTypes.sameAs_B; break;
				case "SAME3": this.paratypes[i]=parTypes.sameAs_C; break;				
			}
		}
	}		
		
	public int bits{
		get{return this.binpattern.Length;}
	}
		
	/**
	 * Replaces HIBYTE( and LOBYTE( macros
	 */
	private string replaceHiLoByte(string line){
		Regex hi=new Regex(@"HIBYTE\((\w+)\)");
		Regex lo=new Regex(@"LOBYTE\((\w+)\)");
		long number;
		if (line.Contains("HIBYTE(")){
			number=BinOperations.string2number(hi.Split(line)[1]);
			line=line.Replace("HIBYTE("+number.ToString()+")",(number&0xFF00).ToString());
		}
		if (line.Contains("LOBYTE(")){
			number=BinOperations.string2number(lo.Split(line)[1]);
			line=line.Replace("LOBYTE("+number.ToString()+")",(number&0xFF).ToString());
		}
		return line.Trim();
	}

	/**
	 * Quick check without throwing errors: Is this going to compile
	 */
	public bool matches(string command){
		return this.filter.IsMatch(BinOperations.seekAndConvertBase10((replaceHiLoByte(command))));
	}
		
	/**
	 * Quick check:Is binary value matching with this opcode (unasseble)
	 */		
	public bool matches(long binary){
		string sBin=BinOperations.number2binString(binary,this.binpattern.Length);
		if (sBin.Length>this.binpattern.Length)
			return false;
		for(int i=0;i<this.binpattern.Length;i++){
			if ((sBin[i]=='1')&&(this.binpattern[i]=='0')){return false;}
			if ((sBin[i]=='0')&&(this.binpattern[i]=='1')){return false;}
		}
		return true;
	}

		
	/**
	 * Does final compile
	 */
	public string compile(string command,long address){
		if (!this.matches(command)) throw new Exception("Command does not match to regexp");
		//Kaivetaan parametrit stringistÃ¤
		char[] characters={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','X','Y'};
			
		string[] raakaparametrit=this.filter.Split(BinOperations.seekAndConvertBase10(replaceHiLoByte(command)));
			
		//Find real parameters from line
		ArrayList parameters=new ArrayList();
		foreach(string s in raakaparametrit){
			if((s.Trim()).Length>0){
				try{
					long number= Convert.ToInt64(s,10);  //If conversion fails, just don't add that to parameters 
					parameters.Add(number);	//HACK!!!!   :(
				}catch{}
			}
		}
			
		string result=this.binpattern;
		int parnumber=0;
		long parvalue=0;
		for(int i=0;i<this.paratypes.Length;i++){
			if (parnumber<parameters.Count)
				parvalue=(long)parameters[parnumber];
			switch(this.paratypes[i]){
				case parTypes.adrRel:	
					parvalue=parvalue-address-1;
				break;
				case parTypes.copy:
					parnumber--; //Use previous value (copy from previous parameter value)
					parvalue=(long)parameters[parnumber];
				break;
				case parTypes.val16to32:
					parvalue=parvalue-16;
				break;
			}
			
			//Same as_A or Same as_B .... _Z
			if ((this.paratypes[i]>=parTypes.sameAs_A)){
				long par_now=(long)parameters[parnumber];
				int compareParnumber=(int)this.paratypes[i]-(int)parTypes.sameAs_A;
				long par_compare=(long)parameters[compareParnumber];
				if (par_compare!=par_now)
					throw new Exception("Same as requirement failure!! for "+ command+"  "+par_compare.ToString()+"!="+par_now.ToString());
			}				
			parnumber++;
			result=storePar(result,characters[i],parvalue);
		}
			
		if(result.Replace("0","").Replace("1","").Length>0)
			throw new Exception("Internal error: all parameters are not replaced in command string "+result+"");
		return result;		
	}	
}
